#pragma onec

#include <iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <cstdlib>
#include <cstring>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/wait.h>
#include <signal.h>
#include <pthread.h>
#include "log.hpp"
#include "ThreadPool.hpp"
#include "Task.hpp"
#include "daemon.hpp"
using namespace std;

const int defaultsockfd = -1;
const string defaultip = "0.0.0.0";
const int backlog = 10; // 但是一般不要设置的太大
extern Log lg;

enum
{
    UsageError = 1,
    SocketError = 2,
    BindError = 3,
    ListenError = 4
};

class TcpServer;

class ThreadData
{
public:
    ThreadData(int fd, const std::string &ip, const uint16_t &p, TcpServer *t)
    : sockfd(fd)
    , clientip(ip)
    , clientport(p)
    , tsvr(t)
    {}
public:
    int sockfd;
    string clientip;
    uint16_t clientport;
    TcpServer *tsvr;
};


class TcpServer
{
public:
    TcpServer(const uint16_t &port, const string &ip = defaultip)
        : _listensocket(defaultsockfd), _port(port), _ip(ip)
    {
    }
    void Init()
    {
        // 1.创建套接字
        _listensocket = socket(AF_INET, SOCK_STREAM, 0);
        if (_listensocket < 0) // 创建套接字失败
        {
            lg(Fatal, "create socket error, errno : %d, errstring: %s", errno, strerror(errno));
            exit(SocketError);
        }
        lg(Info, "create socket success, listensocket: %d", _listensocket);

        int opt = 1;
        setsockopt(_listensocket, SOL_SOCKET, SO_REUSEADDR|SO_REUSEPORT, &opt, sizeof(opt)); // 防止偶发性的服务器无法进行立即重启(tcp协议的时候再说)

        // 2.绑定端口号
        //  使用这个结构体需要包头文件
        struct sockaddr_in local;
        memset(&local, 0, sizeof(local));
        local.sin_family = AF_INET;
        // 转化为网络序列
        local.sin_port = htons(_port);
        // 字符串转化为点时分形式的ip
        inet_aton(_ip.c_str(), &(local.sin_addr));
        // local.sin_addr.s_addr = INADDR_ANY;
        // 此时我们仅仅只在用户栈填好了,并没有写进到打开的网络文件和套接字中,没有设置系统中
        int n = bind(_listensocket, (const struct sockaddr *)&local, sizeof(local));
        if (n < 0)
        {
            lg(Fatal, "bind error, errno : %d, errstring: %s", errno, strerror(errno));
            exit(BindError);
        }
        lg(Info, "bind socket success, listensocket: %d", _listensocket);

        // 3.设置监听状态
        // Tcp是面向连接的，服务器一般是比较“被动的”，服务器一直处于一种，一直在等待连接到来的状态
        if (listen(_listensocket, backlog) < 0)
        {
            lg(Fatal, "listen error, errno: %d, errstring: %s", errno, strerror(errno));
            exit(ListenError);
        }
        lg(Info, "bind socket success, listensocket: %d", _listensocket);
    }

    void Start()
    {
        //signal(SIGPIPE, SIG_IGN);
        Daemon(); //进入是父进程,出来是子进程
        ThreadPool<Task>::GetInstance()->Start();
        lg(Info, "tcpServer is running....");
        for (;;)
        {
            // 1. 获取新连接 - 知道客户端的ip地址和端口号
            struct sockaddr_in client;
            socklen_t len = sizeof(client);
            // _sockfd的核心工作是: 从底层获取客户端的请求 - 餐厅门口的迎宾
            // sockdf的核心工作是: 处理会去来的客户请求 - 餐厅的服务员
            int sockfd = accept(_listensocket, (struct sockaddr *)&client, &len);
            if (sockfd < 0)
            {
                // 从底层获取客户端的请求 - 餐厅门口的迎宾 - 路人不来吃饭 - 换一下批路人
                lg(Warning, "accept error, errno: %d, errstring: %s", errno, strerror(errno)); //?
                continue;                                                                      // 所以这里使用continue
            }
            lg(Info, "get a new link..., sockfd: %d", sockfd);

            // 2.根据新连接来进行通信
            // 获取客户端的ip地址和端口号
            uint16_t clientport = ntohs(client.sin_port);
            char clientip[32];
            // 转化成主机序列
            inet_ntop(AF_INET, &(client.sin_addr), clientip, sizeof(clientip));
            cout << "clientport: " << clientport << ", clientip: " << clientip << endl;
            // 单进程版本
            // Service(sockfd, clientip, clientport);
            // close(sockfd);

            // 多进程版本
            // pid_t id = fork();
            // if(id == 0)
            //{
                // 子进程
                // 子进程会继承父进程的文件描述符
                // close(_listensocket);
                // if(fork() > 0) exit(0); // 此时子进程退出了
                // Service(sockfd, clientip, clientport); // 孙子进程执行
                // 对于孙子进程,它的父进程已经退出了,此时孙子进程被系统领养
                // close(sockfd);
                // exit(0);
            //}
            // 父进程
            // 文件描述符使用的是引用计数
            // 关闭父进程的文件描述符不会影响子进程
            // close(sockfd);
            // 这里等待回收子进程的方式不能是阻塞等待
            // pid_t rid = waitpid(id, nullptr, 0);

            // 多线程版本
            // ThreadData* td = new ThreadData(sockfd, clientip, clientport, this);
            // pthread_t tid;
            // pthread_create(&tid, nullptr, Rountine, td);
            // 这里不用join,因为它是阻塞等待
            // pthread_join(tid, nullptr);

            // 线程池版本
            Task t(sockfd, clientip, clientport);
            // 单例模式
            ThreadPool<Task>::GetInstance()->Push(t);
        }
    }

    //static void* Rountine(void* args)
    //{
       // pthread_detach(pthread_self()); // 设置分离状态
        // 文件描述符共享,此时我们就不能关闭
        // 多线程只拥有tcb
        //ThreadData *td = static_cast<ThreadData *>(args);
        // static静态成员方法无法使用非静态成员方法和成员
        // 1.将Service放到TcpServer类外
        // 2.将当前对象的this指针传入
        //td->tsvr->Service(td->sockfd, td->clientip, td->clientport);
        //delete td;
        //return nullptr;
    //}

    ~TcpServer()
    {
        close(_listensocket);
    }

private:
    int _listensocket; // 套接字
    uint16_t _port;    // 端口号
    string _ip;        // ip地址
};